﻿using System;
using System.Configuration;
using System.Data;
using System.DirectoryServices;
using System.DirectoryServices.Protocols;
using System.Data.Common;
using System.Net;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
namespace DataImportService
{
    /// <summary>
    /// Encapsulates the business logic that integrates with an ADAM Server 
    /// over LDAP.
    /// </summary>
    public class DirectoryServer
    {
        #region Fields
        private LdapConnection connection;
        /// <summary>
        /// The key for the Server's path in the AppSettings configuration.
        /// </summary>
        private const string LDAPServerNameAndPort = "LDAPServerNameAndPort";

        /// <summary>
        /// The key for the LDAP Server's Search Base in the AppSettings 
        /// configuration.
        /// </summary>
        private const string LDAPSearchBaseDn = "LDAPSearchBaseDn";

        /// <summary>
        /// The key for the LDAP Server's domain in the AppSettings 
        /// configuration.
        /// </summary>
        private const string LDAPAccountDomain = "LDAPAccountDomain";
        /// <summary>
        /// The key for the LDAP Server user's username in the AppSettings
        /// configuration.
        /// </summary>
        private const string LDAPAccountName = "LDAPAccountName";

        /// <summary>
        /// The key for the LDAP Server user's password in the AppSettings
        /// configuration.
        /// </summary>
        private const string LDAPAccountPwd = "LDAPAccountPwd";

        /// <summary>
        /// The Server's path.
        /// </summary>
        private static readonly string LdapServerNameAndPort;

        /// <summary>
        /// The LDAP Server's path.
        /// </summary>
        private static readonly string LdapSearchBaseDn;

        /// <summary>
        /// The domain of the credentials used to access the LDAP Server.
        /// </summary>
        private static readonly string LdapAccountDomain;

        /// <summary>
        /// The username of the credentials used to access the LDAP Server.
        /// </summary>
        private static readonly string LdapAccountName;

        /// <summary>
        /// The password of the credentials used to access the LDAP server.
        /// </summary>
        private static readonly string LdapAccountPwd;

        static string connectionString = ConfigurationManager.ConnectionStrings["CMT"].ConnectionString;

        #endregion Fields

        /// <summary>
        /// Initializes static members of the <see cref="ADAMDirectoryServer"/> 
        /// class.  The members are populated from the application's 
        /// AppSettings configuration.
        /// </summary>
        static DirectoryServer()
        {
            LdapServerNameAndPort = ConfigurationManager.AppSettings[LDAPServerNameAndPort];
            LdapAccountName = ConfigurationManager.AppSettings[LDAPAccountName];
            LdapAccountPwd = ConfigurationManager.AppSettings[LDAPAccountPwd];
            LdapSearchBaseDn = ConfigurationManager.AppSettings[LDAPSearchBaseDn];
            LdapAccountDomain = ConfigurationManager.AppSettings[LDAPAccountDomain];

        }

        #region IDirectoryServerAdapter Members


        public DataTable GetAllEmployee(DateTime? dt)
        {
            string filter = string.Empty;
            string lastPullDate = string.Empty;
            if (dt.HasValue)
                lastPullDate = dt.Value.ToString("yyyyMMdd") + "000000.0Z";
            
            //if (lastPullDate != string.Empty)
                //  filter = "(&(objectClass=user)(whenChanged>=" + lastPullDate + "))";
               // filter = "(&(objectClass=user)(|(whenChanged>=" + lastPullDate + ")(whenCreated>=" + lastPullDate + ")))";
            //else
            filter = "(&(objectCategory=person)(objectClass=user))";

            List<SearchResultEntry> userList = PagedSearch(filter);
            if (userList != null)
            {
                return ToDataTable(userList);
            }
            return null;
        }

        /// <summary>
        /// Convert result to DataTable
        /// </summary>
        /// <param name="userList"></param>
        /// <returns></returns>
        public DataTable ToDataTable(List<SearchResultEntry> userList)
        {
            DataTable dtUsers = null;
            DataRow dr;
            try
            {
                dtUsers = new DataTable();
                dtUsers.TableName = "Users";
                dtUsers.Columns.Add("GUI", typeof(string));
                dtUsers.Columns.Add("GPN", typeof(string));
                dtUsers.Columns.Add("LAST_NAME", typeof(string));
                dtUsers.Columns.Add("MIDDLE_NAME", typeof(string));
                dtUsers.Columns.Add("FIRST_NAME", typeof(string));
                dtUsers.Columns.Add("PREFERRED_NAME", typeof(string));
                dtUsers.Columns.Add("Rank", typeof(string));
                dtUsers.Columns.Add("BusinessUnit", typeof(string));
                dtUsers.Columns.Add("OperatingUnit", typeof(string));
                dtUsers.Columns.Add("ManagementUnit", typeof(string));
                dtUsers.Columns.Add("SMU", typeof(string));
                dtUsers.Columns.Add("NetworkID", typeof(string));
                dtUsers.Columns.Add("NAME", typeof(string));
                dtUsers.Columns.Add("InternetEmail", typeof(string));
                dtUsers.Columns.Add("CountryCode", typeof(string));
                dtUsers.Columns.Add("EmploymentStatus", typeof(string));
                dtUsers.Columns.Add("WhenChanged", typeof(DateTime));


                for (int row = 0; row < userList.Count; row++)
                {
                    dr = dtUsers.NewRow();
                    SearchResultEntry _usr = (SearchResultEntry)userList[row];
                    if (_usr.Attributes.Contains("c"))
                    {
                        dr["CountryCode"] = Convert.ToString(_usr.Attributes["c"][0]);
                    }
                    if (_usr.Attributes.Contains("mail"))
                    {
                        dr["InternetEmail"] = Convert.ToString(_usr.Attributes["mail"][0]);
                    }
                    if (_usr.Attributes.Contains("ey-gui"))
                    {
                        dr["GUI"] = Convert.ToString(_usr.Attributes["ey-gui"][0]);
                    }
                    if (_usr.Attributes.Contains("ey-gpn"))
                    {
                        dr["GPN"] = Convert.ToString(_usr.Attributes["ey-gpn"][0]);
                    }
                    if (_usr.Attributes.Contains("givenname"))
                    {
                        dr["FIRST_NAME"] = Convert.ToString(_usr.Attributes["givenname"][0]);
                    }
                    if (_usr.Attributes.Contains("middlename"))
                    {
                        dr["MIDDLE_NAME"] = Convert.ToString(_usr.Attributes["middlename"][0]);
                    }
                    if (_usr.Attributes.Contains("sn"))
                    {
                        dr["LAST_NAME"] = Convert.ToString(_usr.Attributes["sn"][0]);
                    }
                    if (_usr.Attributes.Contains("ey-ad-displayName"))
                    {
                        dr["PREFERRED_NAME"] = Convert.ToString(_usr.Attributes["ey-ad-displayName"][0]);
                    }
                    if (_usr.Attributes.Contains("ey-grade"))
                    {
                        dr["Rank"] = Convert.ToString(_usr.Attributes["ey-grade"][0]);
                    }
                    if (_usr.Attributes.Contains("division"))
                    {
                        dr["BusinessUnit"] = Convert.ToString(_usr.Attributes["division"][0]);
                    }
                    if (_usr.Attributes.Contains("ey-Operating-Unit"))
                    {
                        dr["OperatingUnit"] = Convert.ToString(_usr.Attributes["ey-Operating-Unit"][0]);
                    }
                    if (_usr.Attributes.Contains("ey-Management-Unit"))
                    {
                        dr["ManagementUnit"] = Convert.ToString(_usr.Attributes["ey-Management-Unit"][0]);

                    }
                    if (_usr.Attributes.Contains("ey-sub-management-unit"))
                    {
                        dr["SMU"] = Convert.ToString(_usr.Attributes["ey-sub-management-unit"][0]);

                    }
                    if (_usr.Attributes.Contains("ey-AD-Full-SamAccountName"))
                    {
                        dr["NetworkID"] = Convert.ToString(_usr.Attributes["ey-AD-Full-SamAccountName"][0]);

                    }
                    if (_usr.Attributes.Contains("name"))
                    {
                        dr["NAME"] = Convert.ToString(_usr.Attributes["name"][0]);

                    }
                    if (_usr.Attributes.Contains("ey-employment-Status"))
                    {
                        dr["EmploymentStatus"] = Convert.ToString(_usr.Attributes["ey-employment-Status"][0]);

                    }
                    if (_usr.Attributes.Contains("WhenChanged"))
                    {
                        dr["WhenChanged"] = DateTime.ParseExact(Convert.ToString(_usr.Attributes["WhenChanged"][0]), "yyyyMMddHHmmss.f'Z'", CultureInfo.GetCultureInfo("en-GB"));
                    }
                    
                    dtUsers.Rows.Add(dr);
                }
                return dtUsers;
            }
            catch
            {
                return null;
            }
            finally
            {
                dtUsers.Dispose();
                dr = null;
            }

        }

        /// <summary>
        /// perform a paged search of all directory objects from some searchRoot and scope 
        /// the search to include all child containers and their objects
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public List<SearchResultEntry> PagedSearch(string filter)
        {
            GetADAMConnection();
            int pageSize = Convert.ToInt16(1000);
            List<SearchResultEntry> result = new List<SearchResultEntry>();
            string[] baseAttributes = new string[17] {"c","mail","ey-gui","ey-gpn", "givenname","middlename","sn","ey-ad-displayName","ey-grade",
                                                        "division","ey-Operating-Unit","ey-Management-Unit","ey-sub-management-unit","ey-AD-Full-SamAccountName",
                                                        "name","ey-employment-Status","WhenChanged"};

            // create a SearchRequest object
            SearchRequest searchRequest = new SearchRequest(LdapSearchBaseDn, filter, System.DirectoryServices.Protocols.SearchScope.Subtree, baseAttributes);

            // create the PageResultRequestControl object and pass it the size of each page.
            PageResultRequestControl pageRequest =
                new PageResultRequestControl(pageSize);

            // add the PageResultRequestControl object to the
            // SearchRequest object's directory control collection 
            // to enable a paged search request
            searchRequest.Controls.Add(pageRequest);

            // turn off referrals so that data from other partitions is
            // not returned. This is necessary when scoping a paged search
            // to the root of a domain. 
            SearchOptionsControl searchOptions =
                new SearchOptionsControl(SearchOption.DomainScope);

            // add the SearchOptionsControl object to disable referral chasing
            searchRequest.Controls.Add(searchOptions);

            // declare and initialize a variable to track the pages returned
            int pageCount = 0;

            // loop through the pages until there are no more to retrieve
            while (true)
            {
                // increment the pageCount by 1
                pageCount++;

                // cast the directory response into a SearchResponse object
                SearchResponse searchResponse =
                    (SearchResponse)this.connection.SendRequest(searchRequest);

                // verify support for this advanced search operation
                // note while two controls were added, one for paging
                // the other to turn off referral chasing, the server returns
                // only one for paging. The SearchOptionsControl is packaged
                // in the send request, but it not returned in the directory 
                // response.
                if (searchResponse.Controls.Length != 1 ||
                    !(searchResponse.Controls[0] is PageResultResponseControl))
                {
                    throw new Exception("The directory server cannot page the search result set. PageResultResponceControl is missing.");
                }

                // cast the diretory control into a PageResultResponseControl object.
                PageResultResponseControl pageResponse =
                    (PageResultResponseControl)searchResponse.Controls[0];
                // display the entries within this page
                foreach (SearchResultEntry entry in searchResponse.Entries)
                {
                    result.Add(entry);
                }

                // if this is true, there are no more pages to request
                if (pageResponse.Cookie.Length == 0)
                {
                    break;
                }

                // set the cookie of the pageRequest equal to the cookie of the pageResponse 
                // to request the next page of data in the send request
                pageRequest.Cookie = pageResponse.Cookie;
                pageResponse = null;
                searchResponse = null;
            }

            return result;
        }

        /// <summary>
        /// For connecting using a different user name, password and domain name
        /// </summary>
        /// <param name="ldapServerName"></param>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="domainName"></param>
        /// <returns></returns>
        public static LdapConnection Connect(string ldapServerName, string userName, string password, string domainName)
        {
            LdapConnection connection = null;

            // Create an LDAP connection to the server
            connection = new LdapConnection(ldapServerName);
            // Use the encryption capabilities of SSPI to encrypt traffic after the 
            // security context is established.
            connection.SessionOptions.Sealing = true;
            // Use the signing capabilities of SSPI to sign network traffic and verify
            // whether someone has tampered with it.
            connection.SessionOptions.Signing = true;
            NetworkCredential networkCredential = new NetworkCredential(userName, password, domainName);

            // Bind to the ldap server
            bool isAuthenticated = Authenticate(connection, networkCredential);

            if (isAuthenticated)
            {
                return connection;
            }
            else
            {
                return null;
            }
        }
        /// <summary>
        /// Authenticate Connection
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="credentials"></param>
        /// <returns></returns>
        public static bool Authenticate(LdapConnection connection, NetworkCredential credentials)
        {
            try
            {
                connection.Bind(credentials);
                return true;
            }
            catch (LdapException ex)
            {
                if (ex.ErrorCode != 49)
                    throw;

                return false;
            }
        }
        /// <summary>
        /// Get ADAM Connection
        /// </summary>
        private void GetADAMConnection()
        {

            this.connection = Connect(LdapServerNameAndPort, LdapAccountName, LdapAccountPwd, LdapAccountDomain);
            this.connection.Timeout = new TimeSpan(2, 0, 0);
            if (this.connection == null) //Meaning we could not bind based on the parameters passed or configured
            {
                throw new System.Configuration.ConfigurationErrorsException("Could not bind to requested LDAP. Check configuration or supplied parameters");
            }
        }
        #endregion
    }
}
